﻿using System.Collections.Generic;
using System.Linq;
using System.Text;
using CI.Organism;

namespace CI.Mesopotamia
{
    /// <summary>
    /// This Fitness evaluator just keeps giving strength to 
    /// the organisms and if they move and have developed neural activity they are upgraded to the next level
    /// </summary>
    public class LetThemBreed : IFitnessLevelEvaluator
    {
        private readonly SimEnv _simEnv = SimEnv.Instance;
        private readonly Dictionary<IOrganism, IList<bool>> _organismsMovementResults = new Dictionary<IOrganism, IList<bool>>();
        private readonly Dictionary<IOrganism, bool> _organismAlreadyMetNeuralRequirments = new Dictionary<IOrganism, bool>();
        private readonly Dictionary<IOrganism, bool> _organismAlreadyMetMovementRequirments = new Dictionary<IOrganism, bool>();

        private readonly Dictionary<IOrganism, ILocation> _organismsLastLocation = new Dictionary<IOrganism, ILocation>();

        private const int MaxSampleCount = 100;
        private const int MinSampleCount = 50;
        private const int MovementPercentCount = 20;

        /// <summary>
        /// Added CIET amount of  strength each time
        /// </summary>
        /// <param name="organism"></param>
        public void UpdateStrength(IOrganism organism)
        {
            _simEnv.TraceLogger.LogDebug("Updating Organism strength");
            organism.Energy += organism.InitCIET;
        }

        /// <summary>
        /// If they moved a certain % of the time with a minimum sample set
        /// and they have more than one neuron and one synapse
        /// </summary>
        /// <param name="organism"></param>
        /// <returns></returns>
        public bool UpgradeToNextLevel(IOrganism organism)
        {
            // if it hasnt met the neural requirements abort
            if (!MetNeuralRequirements(organism))
                return false;

            // if it hasnt met the movement requirements abort
            if (!MetMovementRequirements(organism))
                return false;

            return true;
        }


        /// <summary>
        /// Have they moved enough
        /// </summary>
        /// <param name="organism"></param>
        /// <returns></returns>
        private bool MetMovementRequirements(IOrganism organism)
        {
            lock (_organismsMovementResults)
            {
                if (_organismAlreadyMetMovementRequirments.ContainsKey(organism))
                    return true;

                // if not exist create list
                if (!_organismsMovementResults.ContainsKey(organism))
                {
                    _organismsMovementResults[organism] = new List<bool>();
                }
                else
                {
                    // if over size trim the first one
                    if (_organismsMovementResults[organism].Count > MaxSampleCount)
                        _organismsMovementResults[organism].RemoveAt(0);

                }

                _organismsMovementResults[organism].Add(HasMovedBodyRadius(organism));

                var result = HasMovedEnough(_organismsMovementResults[organism]);

                if (result)
                    _organismAlreadyMetMovementRequirments[organism] = true;

                return result;
            }
        }

        private bool MetNeuralRequirements(IOrganism organism)
        {
            if (_organismAlreadyMetNeuralRequirments.ContainsKey(organism))
                return true;

            if (organism.NeuralSystem == null)
                return false;

            if (!((organism.NeuralSystem.Synapses.Count > 1) || (organism.NeuralSystem.Neurons.Count > 1)))
                return false;

            _simEnv.TraceLogger.LogDebug("MetNeural Requirements passed");
            _simEnv.TraceLogger.LogDebug(string.Format("Synapse count {0}", organism.NeuralSystem.Synapses.Count));
            _simEnv.TraceLogger.LogDebug(organism.NeuralSystem.Synapses.ToString());
            _simEnv.TraceLogger.LogDebug(string.Format("Neurons count {0}", organism.NeuralSystem.Neurons.Count));
            _simEnv.TraceLogger.LogDebug(organism.NeuralSystem.Neurons.ToString());

            _organismAlreadyMetNeuralRequirments[organism] = true;

            return true;
        }

        /// <summary>
        /// Has the organism moved enough lookat back at its history
        /// </summary>
        /// <returns></returns>
        private bool HasMovedEnough(ICollection<bool> movementHistory)
        {
            if (movementHistory.Count < MinSampleCount)
                return false;

            var count = movementHistory.Where(x => x).Count();

            _simEnv.TraceLogger.LogDebug("Movement History : " + movementHistory.Count);
            _simEnv.TraceLogger.LogDebug("Movement count :" + count);

            return count > (movementHistory.Count / (100 / MovementPercentCount));
        }

        private bool HasMovedBodyRadius(IOrganism organism)
        {
            bool result = false;
            if (organism.Organisms.DirtyLocations.Contains(organism))
            {
                ILocation lastLocation;
                ILocation compareLocation = null;
                ILocation currentLocation = organism.Location;
                _organismsLastLocation.TryGetValue(organism, out lastLocation);
                

                if (lastLocation != null)
                    compareLocation = new Location(lastLocation.XAxis, lastLocation.YAxis, lastLocation.ZAxis, 0, 0, 0);

                if (compareLocation == null || !LocationCollisions.LocationsCollide(currentLocation, compareLocation))
                {
                    _organismsLastLocation[organism] = currentLocation;
                    result = true;
                }
            }

            _simEnv.TraceLogger.LogDebug(string.Format("HasMovedBodyRadius on {0} with result of {1}", organism.Guid, result));
            return result;
        }


        ///<summary>
        /// Organism died, so remove it.
        ///</summary>
        ///<param name="organism"></param>
        public void RemoveOrganism(IOrganism organism)
        {
            lock (_organismsMovementResults)
            {
                if (_organismsMovementResults.ContainsKey(organism))
                    _organismsMovementResults.Remove(organism);
            }

        }


        ///<summary>
        /// Dump info about the the neural and movement of an organism.
        ///</summary>
        ///<param name="organism"></param>
        ///<returns></returns>
        public string ToString(IOrganism organism)
        {
            var result = new StringBuilder();

            // dump basic info
            result.AppendLine("Dumping organism in Let them breed - Start : " + organism.Guid);
            result.AppendLine(organism.ToString());

            // dump neural system
            result.AppendLine(organism.NeuralSystem.ToString());

            // dump each neuron
            foreach (var neuron in organism.NeuralSystem.Neurons)
            {
                result.AppendLine(neuron.ToString());
                result.AppendLine("Input Synapses");
                foreach (var synapse in neuron.InputSynapse)
                {
                    result.AppendLine(synapse.ToString());
                }
                result.AppendLine("Output Synapses");
                foreach (var synapse in neuron.OutputSynapse)
                {
                    result.AppendLine(synapse.ToString());
                }
                result.AppendLine(System.Environment.NewLine);
            }

            // dump movement into
            result.AppendLine("Movement samples counted : " + _organismsMovementResults[organism].Count);
            result.AppendLine("Movement occurances counted : " +
                              _organismsMovementResults[organism].Where(x => x).Count());

            result.AppendLine("Dumping organism in Let them breed - End : " + organism.Guid);

            return result.ToString();
        }
    }
}
